home *** CD-ROM | disk | FTP | other *** search
- -= Voxel Renderer =-
-
- Files Included:
-
- VOXEL.C - Watcom C/C++ Source code for heightfield render
- VOXEL.EXE - Executable form of the source code
- VOXEL.DOC - This documentation file
-
- Instructions:
-
- At your DOS prompt, type "VOXEL" and press enter. If you have a mouse
- installed, use it to move around. If you do not, the screen will move on
- its own.
-
- This executable is Win95 friendly.
-
-
- How it works:
-
- To begin with, I set up 2 arrays. Each array is the width of the area I
- want to render (in this case its 320 for mode 13h). In each index of the
- array I keep height and color information. These buffers can be refered to
- as a top buffer and bottom buffer. I refer to them as Buf1 and Buf2 in my
- code (Buf1 would be the top buffer).
-
- To trace the area to be rendered, I texture map the triangle created by
- the view system. I start with the line in the view system between the points
- (x1,y1) and (x2,y2). This point is the maximum depth of rendering and is
- also a special case. This is a line of constant z, so perspective handling
- will only need to be handled once for this entire line. The perspective
- transform I use can be found in any documentation on 3D renderind:
-
- y' = ScreenMidY - (Y * Distance) / Z
-
- We want to store these perspective transforms in our "top" buffer. To do
- this, we have to stretch the (x1, y1),(x2, y2) line to the rendering width
- (which is 320 in this case). We then count for the screen width (320), using
- the texture value (the texture is our height map) as the Y in the perspective
- transform. The Z value we use in the maximum depth used in rendering. We also
- need the color at that particular point. For simplicity sake, we will use the
- map color. Here is a piece of code to illistrate how this is done.
-
- for (x = 0; x < 320; x++)
- {
- topbuffer[x].y = 99 - (heightmap[xval >> 8 + yval & 0xff00]*Distance)/z;
- topbuffer[x].c = heightmap[xval >> 8 + yval & 0xff00]
- yval += ystep;
- xval += xstep;
- }
-
- We are using 24.8 fixed point for the texturing coordinates and using
- a 256x256 map. As you may have figured out, this does not provide any
- smoothing, which is ok. Since we are at the furthest point from the viewer
- sampling every point looks the same as interpolating between heights (this
- is why its a special case, along with writing to the "top" buffer).
-
- With the furthest line done, we are ready to handle the meat of the
- rendering. The intermediate lines make up the greatest portion of the view
- so we will take great care to make sure they look the best. For starters,
- increment the texture values so they are one step closer to the viewer.
- You are now ready to enter the intermediate section. For this section you
- loop from the maximum depth - 1, to the nearest depth + 1. The basic process
- for rendering this section is to read the map values, interpolate the heights
- and colors, render the horizontal lines, increment the texture coordinates,
- then repeat.
-
- Reading the map values for this section is a bit different than the furthest
- line case. For this we only want to do a perspective transform on the first
- point in each heightfield we reach. This process is actually a little trickey,
- but still quite fast. To do this properly, we must keep a secondary list that
- contains the X values of the points we actually perspective transform.
- Ideally these points will be 2-4 pixels apart, maybe more. We also need a
- way to tell if we have moved to a new index in our map, which tells us to
- record the X value. This is actually fairly simple. We keep an old value
- and a current value. When the current value is the same as the old value,
- we are at the same map location. To simplify this even more, you can keep
- old and current values as an offset instead of coordinates. The thought might
- arise concering what use initialize the old value to before the loop begins.
- This is simple. Initialize it to a number that is impossible for the index
- to reach, such as -1. Since I know you are throroughly confused at this point
- I will give you a small piece of code to illistrate what is going on.
-
-
- oldindex = -1; // initialize old value
- xindex = 0; // initialize x index table counter
-
- for (x = 0; x < 320; x++)
- {
- index = (xval >> 8) + (yval & 0xff00); // get curent index value
- if (index != oldindex)
- {
- bottombuffer[x].y = 99 - (heightmap[index]*Distance)/z;
- bottombuffer[x].c = heightmap[index]
-
- xtable[xindex] = x; // record X value
- xindex++;
-
- oldindex = index;
- }
- yval += ystep;
- xval += xstep;
- }
-
-
- Once that loop is complete, we have a list of the columns we did a
- perspective transform on, and we skipped any duplicate transforms. Also
- notice that we are saving to the BottomBuffer instead of the top. When
- doing the intermidate lines, we always store to the bottom buffer. A subtle
- point to this process is what if the right most point (x = 319) is still
- equal to the old index? We won't get a closing value in our xtable list
- resulting in fluctuating values making for an ugly display. The easiest way
- to fix this is to modify the IF statement to read:
-
- if (index != oldindex) || (x = 319)
-
- This will always make sure we have an ending (the -1 always makes sure we
- have a beginning).
-
- When that loop is finished, you are ready to perform the magic. This little
- step is what makes the land so smooth. You must interpolate the height and
- the color between the xtable points. A simple linear interpolation is all
- thats necessary to provide a convincing look. Since you know the number of
- xtable entries, and you know the x values at each of those entries,
- interpolatoin is straight forward. During the interpolation, be sure to
- store the inbetween values back into the bottom buffer. Here is how I do it.
-
- for (i = 0; i < (xindex - 1); i++)
- {
- cval = bottomtable[xtable[i]].c << 8;
- yval = bottomtable[xtable[i]].y << 8;
- cstep = ((bottomtable[xtable[i+1]].c - bottomtable[xtable[i]].c) << 8) /
- (xtable[i+1] - xtable[i]);
- ystep = ((bottomtable[xtable[i+1]].y - bottomtable[xtable[i]].y) << 8) /
- (xtable[i+1] - xtable[i]);
-
- for (j = xtable[i]+1; j < xtable[i+1]; j++)
- {
- cval += cstep;
- yval += ystep;
- bottomtable[j].y = yval >> 8;
- bottomtable[j].c = cval >> 8;
- }
- }
-
- There are a few subtle points in this loop. The first is i looping from
- 0 to xindex - 2 ( < xindex - 1 ). This is because we sometimes index xtable
- with an i+1. Looping to xindex - 1 would go beyond the bounds of the array
- esulting in bogus data. The next point is j looping from xtable[i]+1. This
- is because we already know the value at xtable[i]. No need to re-store the
- value.
-
- The difficult part of the routine is now finished. From here, you call a
- function to draw vertical lines from the top buffer to the bottom buffer.
- The function should do some sort of color interpolation. Also note that
- this routine should only draw a vertical line if the top buffer value
- is less than the bottom buffer value.
-
- After you draw the vertical lines, copy the values in the bottom buffer
- to the top buffer. This will prime the tables for the next time around.
- Increment your texture values once again, then your loop should continue.
-
- Once you have reached the end of that loop, you are ready for the nearest
- line. This line is also a special case, but is much easier to handle than
- the other two cases. Entering this loop, we have the top buffer set with
- the last values we calculated, and we are at the closest point to the viewer
- in the texture. In the last loop, we go just like the first loop (furthest
- depth), bu instead of doing a perspective transform to get the height, just
- store the value 199. Since this is the bottom of the screen, it automatically
- gets rid of any holes in the rendering. You still need to interpolate the
- texture values though in order to get the color. When looping, store the
- values in the bottom buffer. Here is code on how to do it.
-
- for (x = 0; x < 320; x++)
- {
- bottombuffer[x].y = 199;
- bottombuffer[x].c = heightmap[xval >> 8 + yval & 0xff00]
- yval += ystep;
- xval += xstep;
- }
-
- With the last loop complete, do another call to the vertical line drawing
- routine. Once that is finished, your landscape is rendered.
-
-
- Notes:
-
- If you have trouble following this explination, I have included full
- working source code of this method. There are a few optimizations in the
- source code that I haven't discussed here.
-
- There are also several shortcomings to this method. The biggest deals
- with rotations. When you rotate to certain angles you get terrible peaks
- on quite a few portions of the display. I know the cause, but I don't
- feel like explaining it (I've typed enough already). You can probably
- get rid of it by doing a conditional rendering based on the slope of the
- lines when doing the texture mapping.
-
-
- If you have any questions or comments, please feel free to contact me.
-
- Alex Chalfin
- achalfin@uceng.uc.edu
-